package org.nstack.network.l3;

import org.nstack.core.db.SimpleQuery;
import org.nstack.core.db.SimpleQuery.Op;
import org.nstack.header.network.l3.*;
import org.nstack.utils.Utils;
import org.nstack.utils.logging.CLogger;
import org.nstack.utils.network.NetworkUtils;

import java.util.Collections;
import java.util.List;
import java.util.Random;

public class RandomIpAllocatorStrategy extends AbstractIpAllocatorStrategy {
    private static final CLogger logger = Utils.getLogger(RandomIpAllocatorStrategy.class);
    public static final IpAllocatorType type = new IpAllocatorType(L3NetworkConstant.RANDOM_IP_ALLOCATOR_STRATEGY);

    @Override
    public IpAllocatorType getType() {
        return type;
    }

    @Override
    public UsedIpInventory allocateIp(IpAllocateMessage msg) {
        if (msg.getRequiredIp() != null) {
            return allocateRequiredIp(msg);
        }

        SimpleQuery<IpRangeVO> query = dbf.createQuery(IpRangeVO.class);
        query.add(IpRangeVO_.l3NetworkUuid, Op.EQ, msg.getL3NetworkUuid());
        List<IpRangeVO> ranges = query.list();

        Collections.shuffle(ranges);

        do {
            String ip = null;
            IpRangeVO tr = null;

            for (IpRangeVO r : ranges) {
                ip = allocateIp(r);
                tr = r;
                if (ip != null) {
                    break;
                }
            }

            if (ip == null) {

                return null;
            }

            UsedIpInventory inv = l3NwMgr.reserveIp(IpRangeInventory.valueOf(tr), ip);
            if (inv != null) {
                return inv;
            }
        } while (true);
    }

    private String steppingAllocate(long s, long e, int total, String rangeUuid) {
        int step = 254;
        int failureCount = 0;
        int failureCheckPoint = 5;

        while (s <= e) {





            if (failureCheckPoint == failureCount++) {
                SimpleQuery<UsedIpVO> q = dbf.createQuery(UsedIpVO.class);
                q.add(UsedIpVO_.ipRangeUuid, Op.EQ, rangeUuid);
                long count = q.count();
                if (count == total) {
                    logger.debug(String.format("ip range[uuid:%s] has no ip available, try next one", rangeUuid));
                    return null;
                } else {
                    failureCount = 0;
                }
            }

            long te = s + step;
            te = te > e ? e : te;
            SimpleQuery<UsedIpVO> q = dbf.createQuery(UsedIpVO.class);
            q.select(UsedIpVO_.ipInLong);
            q.add(UsedIpVO_.ipInLong, Op.GTE, s);
            q.add(UsedIpVO_.ipInLong, Op.LTE, te);
            q.add(UsedIpVO_.ipRangeUuid, Op.EQ, rangeUuid);
            List<Long> used = q.listValue();
            if (te - s + 1 == used.size()) {
                s += step;
                continue;
            }

            Collections.sort(used);

            return NetworkUtils.randomAllocateIpv4Address(s, te, used);
        }

        return null;
    }

    private String allocateIp(IpRangeVO vo) {
        int total = vo.size();
        Random random = new Random();
        long s = random.nextInt(total) + NetworkUtils.ipv4StringToLong(vo.getStartIp());
        long e = NetworkUtils.ipv4StringToLong(vo.getEndIp());
        String ret = steppingAllocate(s ,e, total, vo.getUuid());
        if (ret != null) {
            return ret;
        }

        e = s;
        s = NetworkUtils.ipv4StringToLong(vo.getStartIp());
        return steppingAllocate(s ,e, total, vo.getUuid());
    }
}
